package wenjie.chen.transfer.controller

import com.fasterxml.jackson.databind.ObjectMapper
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import wenjie.chen.feign.IAccountCommandHandler
import wenjie.chen.transfer.ApplicationTestAware
import wenjie.chen.transfer.commandHandler.impl.TransferCommandHandler
import wenjie.chen.transfer.commandHandler.impl.TransferCommandHandlerCancel
import wenjie.chen.transfer.commandHandler.impl.TransferCommandHandlerConfirm
import wenjie.chen.transfer.dao.AccountDao

import static org.mockito.BDDMockito.given
import static org.mockito.internal.verification.VerificationModeFactory.times
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import static wenjie.chen.transfer.command.fixture.TransferCommandFixture.aTransferCommand

@WebMvcTest
class TransferControllerTest extends ApplicationTestAware {
    private ObjectMapper mapper = new ObjectMapper()

    @Rule
    public ExpectedException thrown = ExpectedException.none()

    @Autowired
    private MockMvc mockMvc

    @MockBean
    private TransferCommandHandler transferCommandHandler

    @MockBean
    private TransferCommandHandlerConfirm transferCommandHandlerConfirm

    @MockBean
    private TransferCommandHandlerCancel transferCommandHandlerCancel

    @MockBean
    private AccountDao accountDao

    @MockBean
    private IAccountCommandHandler accountCommandHandler

    @Test
    void "should execute try and confirm phases when everything is fine"() {
        def transferCommand = aTransferCommand().build()
        mockMvc
            .perform(post("/transfer")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(mapper.writeValueAsString(transferCommand)))
            .andExpect(status().isOk())

        Mockito.verify(transferCommandHandler, times(1)).transfer(transferCommand)
        Mockito.verify(transferCommandHandlerConfirm, times(1)).transfer(transferCommand)
        Mockito.verify(transferCommandHandlerCancel, times(0)).transfer(transferCommand)
    }

    @Test
    void "should not execute confirm and cancel phases when try phase thrown an exception and already rollback"() {
        thrown.expectMessage("IllegalStateException")

        def transferCommand = aTransferCommand().build()
        given(transferCommandHandler.transfer(transferCommand))
            .willThrow(new IllegalStateException())

        mockMvc
            .perform(post("/transfer")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(mapper.writeValueAsString(transferCommand)))
            .andExpect(status().isOk())

        Mockito.verify(transferCommandHandler, times(1)).transfer(transferCommand)
        Mockito.verify(transferCommandHandlerConfirm, times(0)).transfer(transferCommand)
        Mockito.verify(transferCommandHandlerCancel, times(0)).transfer(transferCommand)
    }

    @Test
    void "should not execute confirm and cancel phases when remote try phase thrown an exception and already rollback"() {
        thrown.expectMessage("IllegalStateException")

        def transferCommand = aTransferCommand().build()
        given(accountCommandHandler.increaseAmount(IAccountCommandHandler.IncreaseAmountCommand.of(transferCommand)))
            .willThrow(new IllegalStateException())

        mockMvc
            .perform(post("/transfer")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(mapper.writeValueAsString(transferCommand)))
            .andExpect(status().isOk())

        Mockito.verify(transferCommandHandler, times(1)).transfer(transferCommand)
        Mockito.verify(transferCommandHandlerConfirm, times(0)).transfer(transferCommand)
        Mockito.verify(transferCommandHandlerCancel, times(0)).transfer(transferCommand)
    }
}
